
// Copyright (C) 2011 Luca Piccioni
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

#include "Derm.Simd.h"

#include <cstring>

void DERM_SIMD_API GetCpuInformation(CpuInfo *cpuInfo)
{
    unsigned int maxCalls;
    unsigned int regEax, regEcx, regEdx;

	// Reset returned value
	memset(cpuInfo, 0, sizeof(CpuInfo));

    // Check for CPUID support
	// If bit 21 (22nd bit) in EFLAGS can be toggled, cpuid is supported
    __asm {
        pushfd;
        pop     eax;

		// Get flags
        mov     ebx, eax;
		// Reverse bit 21
        xor     eax, 0x200000;
        push    eax;

		// Set flags
        popfd;
        pushfd;

		// Re-Get flags to compare with previous flag
        pop eax;
        xor eax, ebx;
        mov regEax, eax;
	}
    if (regEax == 0)
        return;		// CPU does not support CPU

	char vendor[13];
	__asm {
        // CPUID (0)
        xor eax, eax;
        cpuid

        // Maximum CPUID input value
        mov     maxCalls,   eax
        // CPU vendor identification
        mov     dword ptr [vendor],     ebx
        mov     dword ptr [vendor+4],   edx
        mov     dword ptr [vendor+8],   ecx
    }
	strncpy(cpuInfo->Vendor, vendor, 12);

    // if maxCalls is greater than 1, check supported features
	if (maxCalls >= 1) {
		__asm {
			// CPUID (1)
			mov     eax,    1h
			cpuid

			mov     regEax,   eax
			mov     regEcx,   ecx
			mov     regEdx,   edx
		}
	}

	// Stepping number (eax:0~3)
	cpuInfo->Stepping = regEax & 0xF;

    // get model number eax:4~7, eax:16~19
	cpuInfo->Model = (regEax >> 4) & 0xF;
	cpuInfo->ModelExtended = (regEax >> 16) & 0xF;

    // Family number eax:8~11, eax:20~27
	cpuInfo->Family = (regEax >> 8) & 0xF;
	cpuInfo->FamilyExtended = (regEax >> 20) & 0xFF;

    // Processor type
	cpuInfo->ProcessorType = (regEax >> 12) & 0x3;

    // check TSC (Time Stemp Counter)
    if ((regEdx >> 4) & 0x1)
		cpuInfo->OtherSupport |= OTHER_TSC;

    //check MMX support
    if ((regEdx >> 23) & 0x1)
		cpuInfo->SimdSupport |= SIMD_MMX;

    if ((regEdx >> 25) & 0x1)
		cpuInfo->SimdSupport |= SIMD_SSE;

    if ((regEdx >> 26) & 0x1)
		cpuInfo->SimdSupport |= SIMD_SSE2;

	if ((regEcx >> 0) & 0x1)
		cpuInfo->SimdSupport |= SIMD_SSE3;
	if ((regEcx >> 8) & 0x1)
		cpuInfo->SimdSupport |= SIMD_SSSE3;

	// ECX bit 27: OSXSAVE instruction supported
	if ((regEcx >> 27) & 0x1) {
		// ECX bit 28: AVX instructions supported
		if ((regEcx >> 28) & 0x1)
			cpuInfo->SimdSupport |= SIMD_AVX;
	}

    // extended CPUID call for AMD cpu
	if(strcmp(cpuInfo->Vendor, "AuthenticAMD") == 0) {
        __asm {
            // call cpuid once again with eax=1
            mov     eax,    80000001h
            cpuid

            // get results into local vars
            mov     regEax,   eax
            mov     regEdx,   edx
        }

        // check MMX support
		cpuInfo->SimdSupport &= ~SIMD_MMX;
        if ((regEdx >> 23) & 0x1)
			cpuInfo->SimdSupport |= SIMD_MMX;

        // check 3DNow support
        if ((regEdx >> 30) & 0x1)
			cpuInfo->SimdSupport |= SIMD_3DNow;

        // check 3DNow2 support
        if ((regEdx >> 31) & 0x1)
			cpuInfo->SimdSupport |= SIMD_3DNow2;
    }

	return;
}